package jutf8search;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Vector;

/**
 * Class implementing the FuzzySearching methods.
 * 
 *
 */
public class FuzzySearch {
	
	//int chunkSize = 20971520; //20mb
	int chunkSize = 14052; 
    static final int ALPHABET_SIZE = 256;
    FuzzySearchInterface s;
	
    /**
     * Constructor method
     *
     * @param algo Specification of search algorithm to use; either "Counting" or "BitapHamming"
     */
    FuzzySearch(String algo)
    {
    	if(algo.equalsIgnoreCase("Counting")) s = new Counting();
    	else if(algo.equalsIgnoreCase("BitapHamming")) s = new BitapHamming();
    	else System.err.println("Invalid algorithm description");
    }

	 /**
	  * Checks to see if the pattern occurs in the file with the specified distance.
	  * 
	  * @param filename The filename of the file to be searched.
	  * @param distance The number of mismatches allowed
	  * @return A boolean representing if the query was found in the text, with up to distance mismatches
	  * @throws IOException If the file can't be opened for some reason
	  */
	public boolean exists(String filename, int distance) throws IOException
    {
		FileIO file = new FileIO(filename, chunkSize, s.getPatternSize());
		byte[] text;
		long pos;
		s.resetMatch();
		while((text = file.readChunk()) != null)
        {
			pos = s.matchNum(text, 1, distance);
			if(pos != -1) return true;
		}
		return false;
	}
	
    /**
     * A function to check for the existence of the loaded query in a file.
     *
     * @param text The text to be searched
     * @param distance The number of mismatches allowed.
     * @return A boolean indicating if the query was found in the file.
     */
    public boolean exists(byte[] text, int distance)
    {
		if(s.matchNum(text, 1, distance) != -1) return true;
		else return false;
	}
	
    /**
     * Find the total number of matches in the byte[] text.
     * 
     * @param text The byte[] to be searched.
     * @param distance The number of mismatches allowed.
     * @return A long representing the number of matches found.
     */
    public long numMatches(byte[] text, int distance)
    {
		return s.numMatches(text, distance);
	}

    /**
     * Find the total number of matches over an entire file.
     * 
     * @param filename The name of the file to search over.
     * @param distance The number of mismatches allowed.
     * @return The number of matches found as a long.
     * @throws IOException If the file can't be opened for whatever reason.
     */
	public long numMatches(String filename, int distance) throws IOException
	{
		FileIO file = new FileIO(filename, chunkSize, s.getPatternSize());
		byte[] text;
		long matches = 0;
		while((text = file.readChunk()) != null)
        {
			matches += s.numMatches(text, distance);
        }
		return matches;
	}
	
	/**
	 * Finds the UTF-8 character position of the first occurence of the pattern in the file.
	 * 
	 * @param filename The name of the file to be searched.
	 * @param distance The number of mismatches allowed.
	 * @return The character position of the first match, -1 if it doesn't exist.
	 * @throws IOException If the file can't be opened for whatever reason.
	 */
	public long charPos(String filename, int distance) throws IOException
	{
		return charPos(filename, 1, distance);
	}
	
	/**
	 * Finds the UTF-8 character position of the matchNum occurence of the pattern in the file.
	 * 
	 * @param filename The name of the file to be searched.
	 * @param matchNum The number of the match to find.
	 * @param distance The number of mismatches allowed.
	 * @return The character position of the match, -1 if it doesn't exist.
	 * @throws IOException If the file can't be opened for whatever reason.
	 */
    public long charPos(String filename, int matchNum, int distance) throws IOException
	{
		FileIO file = new FileIO(filename, chunkSize, s.getPatternSize());
		byte[] text;
		long pos = 0;
		long charPos = 0;
		s.resetMatch();
		while((text = file.readChunk()) != null)
        {
			pos = s.matchNum(text, matchNum, distance);
			if(pos != -1) return charPos+Utilities.countChars(text,pos);
			else charPos += Utilities.countChars(text,text.length-s.getPatternSize()+1);
        }
		return -1;
	}
	
    /**
     * The UTF-8 character position of the first match in the byte[].
     * 
     * @param text The byte[] to be searched over.
     * @param distance The number of mismatches allowed.
     * @return The character position of the first match of the query in text.
     */
    public long charPos(byte[] text, int distance)
    {
		return Utilities.countChars(text, s.matchNum(text, 1, distance));
	}
    
    /**
     * The UTF-8 character position of the matchNum match in the byte[]
     * @param text The byte[] to be searched over.
     * @param distance The number of mismatches allowed.
     * @param matchNum The match to find the character position of.
     * @return The character position of the matchNum match, -1 if no match.
     */
    public long charPos(byte[] text, int distance, long matchNum)
    {
		return Utilities.countChars(text, s.matchNum(text, matchNum, distance));
	}
    
	/**
	 * Returns the byte position of the first match in the byte[].
	 * @param text The byte[] to be searched.
	 * @param distance The number of mismatches allowed.
	 * @return A long representing the byte position of the first match in the array.
	 */
	public long bytePos(byte[] text, int distance)
    {
		return s.matchNum(text, 1, distance);
	}
	
    /**
     * Calculates the byte position of the matchNum-th occurence of the query in the given byte[].
     *
     * @param text The text to search
     * @param matchNum The match number to report
     * @return The byte position of the match, -1 if query not found matchNum times.
     */
	public long bytePos(byte[] text, long matchNum, int distance)
    {
		return s.matchNum(text, matchNum, distance);
	}
	
    /**
     * Calculates the byte position of the first match of the query in the file.
     *
     * @param filename The file to search
     * @throws IOException If there is a problem with reading the file
     * @return The byte position of the first match
     */
	public long bytePos(String filename, int distance) throws IOException
    {
		return bytePos(filename, 1, distance);
	}
	
    /**
     * Calculates the byte position of the matchNum-th occurence of the query in the specified file.
     *
     * @param filename The file to search
     * @param matchNum The match number to report
     * @throws IOException If there is a problem with reading the file
     * @return The byte position of the match, -1 if not found matchNum times.
     */
	public long bytePos(String filename, int matchNum, int distance) throws IOException
	{
		FileIO file = new FileIO(filename, chunkSize, s.getPatternSize());
		byte[] text;
		long pos = 0;
		long totalPos = 0;
		s.resetMatch();
		while((text = file.readChunk()) != null)
        {
			pos = s.matchNum(text, matchNum, distance);
			if(pos != -1) return totalPos+pos;
			else totalPos += file.chunk.length-(s.getPatternSize()-1);
        }
		//return totalPos+s.getPatternSize()-1;
		return -1;
	}
	

    /**
     * Returns a Vector containing the byte positions of every occurence
     *  of the pattern in the given byte[].
     *
     * @param text The text to search
     * @return Vector of results
     */ 
    public Vector<Long> fullSearch(byte[] text, int distance)
    {
        return s.fullSearch(text, distance);
    }

    /**
     * Returns a Vector containing the byte positions of each match of the query
     *  in the given file.
     *
     * @param text The file to search
     * @return Vector of results
     */ 
    public Vector<Long> fullSearch(String filename, int distance) throws IOException
    {
		FileIO file = new FileIO(filename, chunkSize, s.getPatternSize());
		byte[] text;
        Vector<Long> part, results = new Vector<Long>();
        long bytesRead = 0;
        int numChunksRead = 0;
		while((text = file.readChunk()) != null)
        {
			part = s.fullSearch(text, distance);
            for(int i=0; i<part.size(); i++)
            {
                long a = ((Long)(part.get(i))).longValue();
                results.add(new
                        Long(a+bytesRead-((numChunksRead)*(s.getPatternSize()-1))));
            }
            part.clear();
            bytesRead+=text.length;
            numChunksRead++;
        }
        return results;
    }
	
	
	
	/**
	 * Save the search table using Serialization to the file specified.
	 * @param filename The filename to save the search table to.
	 * @throws IOException If the file cannot be written.
	 */
    public void savePreprocess(String filename) throws IOException
    {
		FileOutputStream fos = new FileOutputStream(filename);
		ObjectOutputStream out = new ObjectOutputStream(fos);
		out.writeObject(s);
		out.close();
	}
    
    /**
     * Load a saved search table from the specified file.
     * @param filename The filename to load from.
     * @throws IOException If there was an error reading the file.
     * @throws ClassNotFoundException If the class being loaded cannot be found.
     */
    public void loadPreprocess(String filename) throws IOException, ClassNotFoundException
    {
		FileInputStream fis = new FileInputStream(filename);
		ObjectInputStream in = new ObjectInputStream(fis);
		s = (FuzzySearchInterface)in.readObject();
	}
    
    /**
     * Preprocess the query to make searching faster.
     * @param query The query to be searched for.
     * @throws Exception If there is a problem preprocessing (for example, the query is too short).
     */
    public void preprocess(String query) throws Exception
    {
    	s.preprocess(query);
    }
	
    /**
     * Preprocess the query to make searching faster.
     * @param query The query to be searched for.
     * @throws Exception If there is a problem preprocessing (for example, the query is too short for the given algorithm).
     */
	public void preprocess(byte[] query) throws Exception
    {
		s.preprocess(query);
	}
    
    /**
     * Get the size of the pattern in bytes.
     * @return The size of the pattern in bytes
     */
    public int getPatternSize()
    {
        return s.getPatternSize();
    }

	/**
	 * Set the chunkSize used when reading the file.
	 * @param chunkSize The chunkSize to be used.
	 */
	public void setChunkSize(int chunkSize)
    {
		this.chunkSize = chunkSize;
	}
	
	
	
}
